---
sidebar_position: 3
---

# Часть 2. Продвинутая работа с Docker

## Настройка базы данных

```sh
docker run --rm -d \
 -v mysql:/var/lib/mysql \
 -v mysql_config:/etc/mysql \
 --name mysql \
 -e MYSQL_ROOT_PASSWORD=password \
 -e MYSQL_DATABASE=blog \
 --net cluster \
 mysql
```

Подключимся к рабочей базе и создадим таблицу

```sh
docker exec -it mysql mysql -p
# Вводим пароль из переменной MYSQL_ROOT_PASSWORD, который мы сами задали
```

Создадим табличку **posts** в базе **blog**

```sql
CREATE TABLE blog.posts (
    id INT NOT NULL AUTO_INCREMENT,
    title varchar(255),
    created TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (id)
);
```

Выйдем из контейнера с помощью ctrl + D

## Запускаем Adminer

Запустим Adminer в той же сети

```sh
docker run -d -p 8080:8080 --net cluster --name adminer adminer
```

Подключимся к `http://127.0.0.1:8080/` (или IP вашей ВМ, если у вас бридж-сеть).

В открывшемся интерфейсе подключимся к базе, указав:

| Поле             | Значение | Примечание            |
| ---------------- | -------- | --------------------- |
| Сервер           | mysql    | имя контейнера с СУБД |
| Имя пользователя | root     |                       |
| Пароль           | password |                       |
| База данных      | blog     |                       |

Если все прошло успешно, вы увидете нашу таблицу `posts`.
Перейдем в нее и добавим несколько записей с помощью "Новая запись".
Можно указывать только поле title, т.к. остальные поля генерятся автоматически при создании объекта.

При желании можно создасть записи и с помощью SQL через контейнер с mysql или через SQL-запрос в Adminer:

```sql
INSERT INTO blog.posts (title)
VALUES ("First Post"), ("Second Post"), ("Third Post");
```

## Запускаем свой сервис

Загрузим себе материалы лабораторной:

```bash
git clone https://gitlab.com/tlakatlekutl/devops-lab2 && cd devops-lab2
```

В каталоге `code/` находится пример примитивного API сервиса на Golang. Для того чтобы начать с ним работать, необходимо собрать образ. Для этого воспользуемся следующей [командой](https://docs.docker.com/engine/reference/commandline/build/):

```sh
docker build -t step1 -f step1.Dockerfile code/
```

где:

- опция `-t` задает имя образа, иначе имя будет равно ID образа
- опция `-f` указывает путь к Dockerfile, если ее не указывать докет будет искать файл Dockerfile в текущем каталоге
- `code/` - это конекст для сборки образа, все файлы, которые долны быть доступны во время сборки.

Запустим наш сервис (собранный ранее образ step1), передадим ему параметры подключения к базе через переменные окружения (с помощью опции -e), а также подключим его к контейнеру mysql.

```sh
docker run --net cluster -p 8000:8000 -e MYSQL_DB=blog -e MYSQL_HOST=mysql -e MYSQL_USER=root -e MYSQL_PASS=password step1
```

Проверим работу нашего приложения выполнив команду curl, в ответе должны увидеть json массив с созданными постами.

```sh
curl localhost:8000/posts
```

## Подробнее про сборку образа

Рассмотрим структуру Dockerfile на примере `step1.Dockerfile`.

В начале любого образа должен быть указан базовый образ, на основе которого собирается новый. В нашем случае `FROM golang:1.19.1`, самый базовый возможно образ `FROM scratch`, в нем нет ничего.

`COPY` копирует файл из контекста (папочки, которою вы указываете при `docker build`) в указанное место в образе. Команда создает новый слой в образе;

`RUN` команда - запускает команду в образе, создает новый слой;

Остальные операнды либо создают пустой слой, либо напрямую меняют метаинформацию в образе.

`EXPOSE` показывает докеру, какие порты слушает приложение;
`CMD` Задает команду запуска в контейнере;
`WORKDIR` меняет рабочую директорию;
`USER` меняет пользователя, под которым идет работа;
`ENV` задает переменные окружения;
и т.д. Подробнее можно ознакомиться в [официальной документации](https://docs.docker.com/engine/reference/builder/).

Взглянем на получившиеся слои у образа:

```sh
docker history step1
```

Как было [показано на лекции](../../lectures/docker/intro.md), каждый получившийся слой - это архив с измененными файлами.

## Оптимизируем сборку

Для удобства вынесем переменные окружения в отдельный файл, например `config.env`:

```text
MYSQL_DB=blog
MYSQL_HOST=mysql
MYSQL_USER=root
MYSQL_PASS=password
```

Соберем образ step2:

```sh
docker build -t step2 -f step2.Dockerfile code/
```

Запустим образ step2 и проверим, что он работает, аналогично предыдущему:

```sh
docker run --link mysql -p 8000:8000 --env-file=config.env  step2
```

> **Задание:**
> Сравните образы step1 и step2 (их Dockerfile и history) и опишите различия.

## Многоэтапная сборка

Вы могли заметить, что для работы нашего сервиса необходим только исполняемый файл, тогда возникает вопрос "А зачем нам тащить с собой сборочные зависимости?" Да не нужны они, поэтому существует многоэтапная сборка: вы собираете исполняемый файл в отдельном образе, а затем копируете результат в эксплуатируемый образ.

Соберем, запустим и протестируем образ step3

```sh
docker build -t step3 -f step3.Dockerfile code/
docker run --link mysql -p 8000:8000 --env-file=config.env step3
```

> **Задание:**
> Взгляните на step3.Dockerfile, в чем принципиальное отличие от предыдущих докерфайлов? Оцените docker history.

## Делимся образом - `docker push`

Для ознакомления:

```bash
# Авторизируемся где-нибудь на приватном registry
docker login

# Смотрим что за образ мы хотим загрузить
docker images

# Тэгаем
docker tag XXX:VERSION registry-gitlab.com/user_or_group/XXX:VERSION

# Загружаем
docker push registry-gitlab.com/user_or_group/XXX:VERSION
```
